// xhr-range-loader.js-RangeLoader类-通用的范围加载器
// 重头戏来了，当前面的加载器都不支持的时候，就使用这种通用加载器。
import Log from '../utils/logger.js';
import SpeedSampler from './speed-sampler.js';
import {BaseLoader, LoaderStatus, LoaderErrors} from './loader.js';
import {RuntimeException} from '../utils/exception.js';

// Universal IO Loader, implemented by adding Range header in xhr's request header
class RangeLoader extends BaseLoader {
    // 常规的就不重点介绍了，主要是相较之前的加载器未出现过的或非常不一样的属性详细介绍一下：
    // isSupported() 核心是判断 XMLHttpRequest 对象的响应类型是不是 arraybuffer
    static isSupported() {
        try {
            let xhr = new XMLHttpRequest();
            xhr.open('GET', 'https://example.com', true);
            xhr.responseType = 'arraybuffer';
            return (xhr.responseType === 'arraybuffer');
        } catch (e) {
            Log.w('RangeLoader', e.message);
            return false;
        }
    }

    constructor(seekHandler, config) {
        super('xhr-range-loader');
        this.TAG = 'RangeLoader';

        this._seekHandler = seekHandler;
        this._config = config;
        // _needStash，这里重写为 false
        this._needStash = false;

        // _chunkSizeKBList 常规数据块表，范围从2的7次方到13次方
        this._chunkSizeKBList = [
            128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 5120, 6144, 7168, 8192
        ];
        // currentChunkSizeKB 当前数据块大小，初始化为 384（2的9次方）
        this._currentChunkSizeKB = 384;
        // _currentSpeedNormalized 当前标准化的加载速率
        this._currentSpeedNormalized = 0;
        // _zeroSpeedChunkCount 待加载数据块数量
        this._zeroSpeedChunkCount = 0;

        this._xhr = null;
        // _speedSampler 速率计算器
        this._speedSampler = new SpeedSampler();

        this._requestAbort = false;
        // _waitForTotalLength 是否待加载
        this._waitForTotalLength = false;
        // _totalLengthReceived 是否全部接收
        this._totalLengthReceived = false;
        
        this._currentRequestURL = null;
        this._currentRedirectedURL = null;
        // _currentRequestRange 当前请求范围
        this._currentRequestRange = null;
        // _totalLength 总长度
        this._totalLength = null;  // size of the entire file
        //  _contentLength 内容长度
        this._contentLength = null;  // Content-Length of entire request range
        // _receivedLength 接收长度
        this._receivedLength = 0;  // total received bytes
        // _lastTimeLoaded 当前接收请求子范围的长度
        this._lastTimeLoaded = 0;  // received bytes of current request sub-range
    }

    // 方法
    // 方法也是一样，详略分明destroy()、abort()、_internalOpen(dataSource, range)、_internalAbort()、_onXhrError(e) 不说
    destroy() {
        if (this.isWorking()) {
            this.abort();
        }
        if (this._xhr) {
            this._xhr.onreadystatechange = null;
            this._xhr.onprogress = null;
            this._xhr.onload = null;
            this._xhr.onerror = null;
            this._xhr = null;
        }
        super.destroy();
    }
    get currentSpeed() {
        return this._speedSampler.lastSecondKBps;
    }
    // 加载数据
    open(dataSource, range) {
        // 设置实例的数据源为 dataSource
        this._dataSource = dataSource;
        // 设置实例的加载范围为 range
        this._range = range;
        // 设置实例的状态为连接中
        this._status = LoaderStatus.kConnecting;

        let useRefTotalLength = false;
        if (this._dataSource.filesize != undefined && this._dataSource.filesize !== 0) {
            useRefTotalLength = true;
            this._totalLength = this._dataSource.filesize;
        }
        // 如果实例的已接收总长度为 0
        if (!this._totalLengthReceived && !useRefTotalLength) {
            // 设置是否待加载为 true
            this._waitForTotalLength = true;
            // 调用 _internalOpen，传入实例数据源，0 到 -1 的范围
            this._internalOpen(this._dataSource, {from: 0, to: -1});
        } else {
            // 调用 _openSubRange
            this._openSubRange();
        }
    }
    // 加载指定范围数据
    _openSubRange() {
        // 创建 chunkSize 为实例当前数据块大小 * 1024
        let chunkSize = this._currentChunkSizeKB * 1024;
        // 创建 from 为实例范围起点 + 实例接收长度
        let from = this._range.from + this._receivedLength;
        // 创建 to 为 from + chunkSize
        let to = from + chunkSize;
        // 如果实例的内容长度不为空且 to - 实例范围起点 >= 实例内容长度，校正 to 为实例范围起点 + 实例内容长度 - 1
        if (this._contentLength != null) {
            if (to - this._range.from >= this._contentLength) {
                to = this._range.from + this._contentLength - 1;
            }
        }
        // 设置实例当前请求范围为 {from,to}
        this._currentRequestRange = {from, to};
        // 调用 _internalOpen，传入实例数据源，实例当前请求范围
        this._internalOpen(this._dataSource, this._currentRequestRange);
    }

    _internalOpen(dataSource, range) {
        this._lastTimeLoaded = 0;

        let sourceURL = dataSource.url;
        if (this._config.reuseRedirectedURL) {
            if (this._currentRedirectedURL != undefined) {
                sourceURL = this._currentRedirectedURL;
            } else if (dataSource.redirectedURL != undefined) {
                sourceURL = dataSource.redirectedURL;
            }
        }

        let seekConfig = this._seekHandler.getConfig(sourceURL, range);
        this._currentRequestURL = seekConfig.url;

        let xhr = this._xhr = new XMLHttpRequest();
        xhr.open('GET', seekConfig.url, true);
        xhr.responseType = 'arraybuffer';
        xhr.onreadystatechange = this._onReadyStateChange.bind(this);
        xhr.onprogress = this._onProgress.bind(this);
        xhr.onload = this._onLoad.bind(this);
        xhr.onerror = this._onXhrError.bind(this);

        if (dataSource.withCredentials) {
            xhr.withCredentials = true;
        }

        if (typeof seekConfig.headers === 'object') {
            let headers = seekConfig.headers;

            for (let key in headers) {
                if (headers.hasOwnProperty(key)) {
                    xhr.setRequestHeader(key, headers[key]);
                }
            }
        }

        // add additional headers
        if (typeof this._config.headers === 'object') {
            let headers = this._config.headers;

            for (let key in headers) {
                if (headers.hasOwnProperty(key)) {
                    xhr.setRequestHeader(key, headers[key]);
                }
            }
        }

        xhr.send();
    }

    abort() {
        this._requestAbort = true;
        this._internalAbort();
        this._status = LoaderStatus.kComplete;
    }

    _internalAbort() {
        if (this._xhr) {
            this._xhr.onreadystatechange = null;
            this._xhr.onprogress = null;
            this._xhr.onload = null;
            this._xhr.onerror = null;
            this._xhr.abort();
            this._xhr = null;
        }
    }

    _onReadyStateChange(e) {
        // 从 e.target 中获取 xhr
        let xhr = e.target;
        // 如果 xhr.readyState 是 2
        if (xhr.readyState === 2) {  // HEADERS_RECEIVED
            if (xhr.responseURL != undefined) {  // if the browser support this property
                let redirectedURL = this._seekHandler.removeURLParameters(xhr.responseURL);
                if (xhr.responseURL !== this._currentRequestURL && redirectedURL !== this._currentRedirectedURL) {
                    this._currentRedirectedURL = redirectedURL;
                    if (this._onURLRedirect) {
                        this._onURLRedirect(redirectedURL);
                    }
                }
            }
            // 如果 xhr.status 在 [200,300) 中
            if ((xhr.status >= 200 && xhr.status <= 299)) {
                // 如果实例的是否待加载为 true，结束
                if (this._waitForTotalLength) {
                    return;
                }
                // 设置实例状态为缓冲中
                this._status = LoaderStatus.kBuffering;
            } else {
                // 设置实例状态为出错
                this._status = LoaderStatus.kError;
                // 如果实例存在出错事件处理函数，执行之，传入 HTTP 状态码错误和错误信息
                if (this._onError) {
                    this._onError(LoaderErrors.HTTP_STATUS_CODE_INVALID, {code: xhr.status, msg: xhr.statusText});
                } else {
                    // 否则报 HTTP 状态码无效错误
                    throw new RuntimeException('RangeLoader: Http code invalid, ' + xhr.status + ' ' + xhr.statusText);
                }
            }
        }
    }
    // progress 事件的处理函数
    _onProgress(e) {
        if (this._status === LoaderStatus.kError) {
            // Ignore error response
            return;
        }
        // 如果实例的内容长度不为空
        if (this._contentLength === null) {
            // 初始化一个 openNextRange 为 false
            let openNextRange = false;
            // 如果实例是否待加载为 true
            if (this._waitForTotalLength) {
                // 设置实例是否待加载为 false
                this._waitForTotalLength = false;
                // 设置实例是否全部接收为 true
                this._totalLengthReceived = true;
                // 设置 openNextRange 为 true
                openNextRange = true;

                let total = e.total;
                // 调用 _internalAbort()
                this._internalAbort();
                // 如果 e.total 不为空，设置实例总长度为它
                if (total != null & total !== 0) {
                    this._totalLength = total;
                }
            }
            // 计算当前请求范围的内容长度（设置实例内容长度为实例总长度或 实例范围终点+1 - 实例范围起点）
            if (this._range.to === -1) {
                this._contentLength = this._totalLength - this._range.from;
            } else {  // to !== -1
                this._contentLength = this._range.to - this._range.from + 1;
            }
            // 如果 openNextRange 为 true，调用 _openSubRange，结束
            if (openNextRange) {
                this._openSubRange();
                return;
            }
            // 如果实例存在已知内容长度事件处理函数，执行之，传入实例的内容长度
            if (this._onContentLengthKnown) {
                this._onContentLengthKnown(this._contentLength);
            }
        }
        // 创建 delta 为 e.loaded 实例当前接收请求子范围
        let delta = e.loaded - this._lastTimeLoaded;
        // 设置实例当前接收请求子范围的长度为 e.loaded
        this._lastTimeLoaded = e.loaded;
        // 将实例速率计算器增加 delta 这么多字节
        this._speedSampler.addBytes(delta);
    }
    //  _normalizeSpeed(input)，和 IOCtr 的标准化速率方法实现几乎一样
    _normalizeSpeed(input) {
        let list = this._chunkSizeKBList;
        let last = list.length - 1;
        let mid = 0;
        let lbound = 0;
        let ubound = last;

        if (input < list[0]) {
            return list[0];
        }

        while (lbound <= ubound) {
            mid = lbound + Math.floor((ubound - lbound) / 2);
            if (mid === last || (input >= list[mid] && input < list[mid + 1])) {
                return list[mid];
            } else if (list[mid] < input) {
                lbound = mid + 1;
            } else {
                ubound = mid - 1;
            }
        }
    }
    // load 事件的处理函数
    _onLoad(e) {
        // 如果实例是否待加载为 true，设置实例是否待加载为 false，结束
        if (this._status === LoaderStatus.kError) {
            // Ignore error response
            return;
        }
        if (this._waitForTotalLength) {
            this._waitForTotalLength = false;
            return;
        }

        // 设置实例当前接收请求子范围的长度为 0
        this._lastTimeLoaded = 0;
        // 获取实例速率计算器中的最近一次的速率，保存在 KBps 里
        let KBps = this._speedSampler.lastSecondKBps;
        if (KBps === 0) {
            // 将实例待加载数据块数量加 1
            this._zeroSpeedChunkCount++;
            // 如果实例待加载数据块数量 >= 3，让 KBps 为实例速率计算器中的平均速率
            if (this._zeroSpeedChunkCount >= 3) {
                // Try get currentKBps after 3 chunks
                KBps = this._speedSampler.currentKBps;
            }
        }
        // 如果 KBps 不为 0
        if (KBps !== 0) {
            // 将实例当前速率设置为 KBps
            let normalized = this._normalizeSpeed(KBps);
            // 如果实例当前标准速率不等于标准化后的 KBps，校正实例标准速率为标准化后的 KBps，实例当前数据块大小为标准化后的 KBps
            if (this._currentSpeedNormalized !== normalized) {
                this._currentSpeedNormalized = normalized;
                this._currentChunkSizeKB = normalized;
            }
        }
        // 从 e .target .response 获取 chunk
        let chunk = e.target.response;
        // 将实例的接收长度加上 chunk 的字节长度
        let byteStart = this._range.from + this._receivedLength;
        this._receivedLength += chunk.byteLength;
        // 创建 reportComplete，初始化为 false
        let reportComplete = false;
        // 如果实例的内容长度不为空且实例接收长度 < 实例的内容长度，调用 _openSubRange 继续加载剩下的数据
        if (this._contentLength != null && this._receivedLength < this._contentLength) {
            // continue load next chunk
            this._openSubRange();
        // 否则，设置 reportComplete 为 true
        } else {
            reportComplete = true;
        }
        // 如果实例存在完成数据到达处理函数，执行之，传入 chunk、实例范围起点 + 实例接收长度、实例接收长度
        if (this._onDataArrival) {
            this._onDataArrival(chunk, byteStart, this._receivedLength);
        }
        // 如果 reportComplete 为 true
        if (reportComplete) {
            // 设置实例状态为完成
            this._status = LoaderStatus.kComplete;
            // 如果实例存在完成事件处理函数，执行之，传入实例范围起点和实例范围起点 + 实例接受数据长度 - 1
            if (this._onComplete) {
                this._onComplete(this._range.from, this._range.from + this._receivedLength - 1);
            }
        }
    }

    _onXhrError(e) {
        this._status = LoaderStatus.kError;
        let type = 0;
        let info = null;

        if (this._contentLength && this._receivedLength > 0
                                && this._receivedLength < this._contentLength) {
            type = LoaderErrors.EARLY_EOF;
            info = {code: -1, msg: 'RangeLoader meet Early-Eof'};
        } else {
            type = LoaderErrors.EXCEPTION;
            info = {code: -1, msg: e.constructor.name + ' ' + e.type};
        }

        if (this._onError) {
            this._onError(type, info);
        } else {
            throw new RuntimeException(info.msg);
        }
    }

}

export default RangeLoader;